/* @flow */

import Dep from './dep'
import VNode from '../vdom/vnode'
import { arrayMethods } from './array'
import {
  def,
  warn,
  hasOwn,
  hasProto,
  isObject,
  isPlainObject,
  isPrimitive,
  isUndef,
  isValidArrayIndex,
  isServerRendering
} from '../util/index'

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

/**
 * In some cases we may want to disable observation inside a component's
 * update computation.
 */
export let shouldObserve: boolean = true

export function toggleObserving (value: boolean) {
  shouldObserve = value
}

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates. 收集依赖派发更新
 */
export class Observer {
  // 观测对象
  value: any;
  // 依赖对象
  dep: Dep;
  // 实例计数器
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    // 初始化实例的 vmCount 为0
    this.vmCount = 0
    // def是对 Object.defineperporty封装
    // value对象的__ob__属性 设置为 Observer实例
    // 为传入第四个参数，表示 __ob__不可枚举，只是记录实例对象的
    def(value, '__ob__', this)
    // 数组响应式处理
    if (Array.isArray(value)) {
      // 当前浏览器是否支持对象原型 __proto__
      // 修补数组中的原型方法，如果数组发生变化调用 notify更新视图
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 遍历数组所有元素，是对象的元素会转化为响应式对象，而非所有属性！！！！
      // 下标和length是属性，无法通过 vm.msg[0]更新，因为性能问题
      // 可以使用 vm.msg.splice(0,1,100) 代替
      this.observeArray(value)
    } else {
      // 遍历对象中的每个属性，转换成 setter/getter
      this.walk(value)
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

// helpers

/**
 * Augment a target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * Augment a target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 判断value是否是对象或 VNode实例
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 如果 value 有 __ob__（observer对象）属性 结束，做过响应式就不用再操作了，相当于加了个缓存
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 创建一个observer对象，
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

/**
 * Define a reactive property on an Object.
 * 为一个对象定义一个响应式的属性
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean  // 浅，true只监听对象第一层属性
) {
  // 给每个属性创建收集依赖对象实例
  const dep = new Dep()
  // 获取 obj属性描述符对象
  const property = Object.getOwnPropertyDescriptor(obj, key)
  // 当前属性不可配置 不可被delete删除，被Object.defineProperty重新定义
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  // 如果属性自带set，get
  const getter = property && property.get
  const setter = property && property.set
  // 没getter或有setter，此方法只传了两个参数
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  // 非浅层，对当前 对象的值val 进行处理，如果值也是对象则会对值进行响应式处理
  // 返回属性值对应的 ob对象
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 如果用户预定义了 getter，则value 等于 getter的返回值
      // 否则直接赋属性值
      const value = getter ? getter.call(obj) : val
      // 如果存在当前依赖目标，即watcher 对象，则建立依赖
      if (Dep.target) {
        // dep是为当前属性收集依赖的对象。收集当前的watcher对象到当前属性的dep的subs数组中
        dep.depend()
        // 给当前 属性对应的值 收集依赖，如果值师叔祖，额外操作
        if (childOb) {
          childOb.dep.depend()
          // 如果属性是数组，则特殊处理收集数组对象依赖 例如 data:{arr:[1,2,3]} 此方法处理 [1,2,3]
          // 外层处理 arr
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // 如果用户预定义了 getter，则value 等于 getter的返回值
      // 否则直接赋属性值
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      /* 新旧值是否相等，或者新旧值为 NaN 则不执行*/
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      // 如果只有 getter 无 setter ，只读，直接返回
      if (getter && !setter) return
      // setter存在更新新值
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 新值是对象，深拷贝， 把新值转化getter，setter
      childOb = !shallow && observe(newVal)
      // 派发更新，发布更改的通知
      dep.notify()
    }
  })
}

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target)) // undefined or 原始值（boolean，string，number，symbol） 发警告
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 判断 target 是否是数组，并且key是否是合法索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 传递的索引可以超过当前target数组的length属性，取最大的 设置为 数组的长度
    target.length = Math.max(target.length, key)
    // 通过 splice 对 key位置元素进行替换
    // 此 splice是 重写后带响应式的 splice，非原生，因此调用此方法会触发数组的 dep.notify
    target.splice(key, 1, val)
    return val
  }
  // key 已经存在 target中，直接返回不处理
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  // 取出响应式对象target的 __ob__
  const ob = (target: any).__ob__
  // 如果是 vue实例，或者 是 vm.$data  ($data 的 ob.vmCount=1 其他是0)
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // 如果不是响应式对象，直接返回，
  if (!ob) {
    target[key] = val
    return val
  }
  // 把key 属性设置成响应式，添加getter，setter
  defineReactive(ob.value, key, val)
  // 发送通知 因为我们给 childOb 收集了依赖，因此这里可以触发对象的 notify
  ob.dep.notify()
  return val
}

/**
 * Delete a property and trigger change if necessary.
 */
export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 删除数组指定位置元素，splice里会调用notify
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  // key属性是否直接属于target 而不是继承
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  if (!ob) {
    return
  }
  ob.dep.notify()
}

/**
 * Collect dependencies on array elements when the array is touched, since
 * we cannot intercept array element access like property getters.
 */
function dependArray (value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    // 处理数组时，如果内部嵌套ob对象data:{arr:[{a:1}]}，则对内部对象 depend,（对应的dep添加watcher）
    e && e.__ob__ && e.__ob__.dep.depend()
    // data:{arr:[{a:[1]}]}
    if (Array.isArray(e)) {
      // 递归
      dependArray(e)
    }
  }
}
